%% copy riak-0.14.2 source to learn dynamo implementation and erlang
%% author: lichuang
%% copy source from riak_core_ring_manager.erl

%% @doc the local view of the cluster's ring configuration

-module(edynamo_ring_manager).
-behaviour(gen_server).

-export([start_link/0,
         get_my_ring/0,
         set_my_ring/1,
         ring_trans/2,
         write_ringfile/0,
         do_write_ringfile/1
	]).

-export([init/1,
    handle_call/3,
    handle_cast/2,
    handle_info/2,
    terminate/2,
    code_change/3]).

-define(RING_KEY, edynamo_ring).

start_link() ->
  gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%% @spec get_my_ring() -> {ok, edynamo_ring:edynamo_ring()} | {error, Reason}
get_my_ring() ->
  case mochiglobal:get(?RING_KEY) of
    Ring when is_tuple(Ring) -> {ok, Ring};
    undefined -> {error, no_ring}
  end.

%% @spec set_my_ring(edynamo_ring:edynamo_ring()) -> ok
set_my_ring(Ring) ->
  gen_server:call(?MODULE, {set_my_ring, Ring}, infinity).

ring_trans(Fun, Args) ->
  gen_server:call(?MODULE, {ring_trans, Fun, Args}, infinity).

%% @spec write_ringfile() -> ok
write_ringfile() ->
  gen_server:cast(?MODULE, write_ringfile).

do_write_ringfile(Ring) ->
  {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:universal_time(),
  TS = io_lib:format(".~B~2.10.0B~2.10.0B~2.10.0B~2.10.0B~2.10.0B",
    [Year, Month, Day, Hour, Minute, Second]),
  case app_helper:get_env(edynamo, ring_state_dir) of
    "<nostore>" ->
      nop;
    Dir ->
      Cluster = app_helper:get_env(edynamo, cluster_name),
      FN = Dir ++ "/edynamo_ring." ++ Cluster ++ TS,
      ok = filelib:ensure_dir(FN),
      file:write_file(FN, term_to_binary(Ring))
  end.

%% Set the ring in mochiglobal.  Exported during unit testing
%% to make test setup simpler - no need to spin up a riak_core_ring_manager
%% process.
set_ring_global(Ring) ->
  mochiglobal:put(?RING_KEY, Ring).

%% gen_server callbacks
init(_Args) ->
  Ring = edynamo_ring:fresh(),
  mochiglobal:put(?RING_KEY, Ring),
  % Initial notification to local observers that ring has changed
  edynamo_ring_events:ring_update(Ring),
  {ok, {}}.

handle_call({set_my_ring, Ring}, _From, State) ->
  set_ring_global(Ring),
  % Notify any local observers that the ring has changed (async)
  edynamo_ring_events:ring_update(Ring),
  {reply,ok,State};

handle_call({ring_trans, Fun, Args}, _From, State) ->
  {ok, Ring} = get_my_ring(),
  case catch Fun(Ring, Args) of
    {new_ring, NewRing} ->
      mochiglobal:put(?RING_KEY, NewRing),
      case edynamo_ring:random_other_node(NewRing) of
        no_node ->
          ignore;
        Node ->
          edynamo_gossip:send_ring(Node)
      end,
      edynamo_ring_events:ring_update(NewRing),
      {reply, {ok, NewRing}, State};
    ignore ->
      {reply, not_changed, State};
    Other ->
      error_logger:error_msg("ring_trans: invalid return value: ~p~n", 
        [Other]),
      {reply, not_changed, State}
  end;
handle_call(_Request, _From, State) ->
  {reply,ok,State}.

handle_cast(write_ringfile, State) ->
  {ok, Ring} = get_my_ring(),
  do_write_ringfile(Ring),
  {noreply,State};
handle_cast(_Request, State) ->
  {noreply,State}.

handle_info(_Info, State) ->
  {noreply,State}.

terminate(_Reason, _State) ->
  ok.

code_change(_OldVsn, State, _Extra) ->
  {ok, State}.
